<?php
require SYSTEM_PATH . '/Database.php';

/**
 * 模型抽象类
 *
 * @author vincent
 *        
 */
abstract class Model {
	/**
	 * 模型对应的数据表名
	 *
	 * @var string
	 */
	protected $tableName;
	/**
	 * 数据库操作对象
	 *
	 * @var Database
	 */
	protected $db;
	/**
	 * 参数绑定
	 *
	 * @var array
	 */
	protected $bind = array ();
	/**
	 * 查询表达式参数
	 *
	 * @var array
	 */
	protected $options = array ();
	
	/**
	 * 构造函数
	 */
	public function __construct() {
		$this->tableName = $this->getTableName ();
		$this->db = Database::getInstance ();
	}
	/**
	 * 获取database实例
	 *
	 * @return Database
	 */
	public function getDB() {
		return $this->db;
	}
	/**
	 * 指定查询字段
	 *
	 * @param mixed $field
	 * @return Model
	 */
	public function field($field) {
		if (is_array ( $field )) {
			$field = implode ( $field, ',' );
		}
		$this->options ['field'] = $field;
		return $this;
	}
	/**
	 * 查找单条数据
	 *
	 * @param array $options
	 */
	public function find($options = array()) {
		$rs = $this->limit ( 1 )->select ( $options );
		if ($rs) {
			return $rs [0];
		}
		return NULL;
	}
	/**
	 * 通过索引查找数据
	 *
	 * @param mixed $indexValue 索引值
	 * @param string $indexName 索引名
	 * @return Ambigous <>|NULL
	 */
	public function findByIndex($indexValue, $indexName = 'id') {
		$rs = $this->select ( array (
				array (
						$indexName,
						'=',
						$indexValue 
				) 
		) );
		if ($rs) {
			return $rs [0];
		}
		return NULL;
	}
	/**
	 * 选择数据
	 */
	public function select($options = array()) {
		if (! isset ( $this->options ['field'] )) {
			$this->options ['field'] = '*';
		}
		if (! empty ( $options )) {
			$this->where ( $options );
		}
		$sql = 'SELECT ' . $this->options ['field'] . ' from ' . $this->tableName;
		$sql .= isset ( $this->options ['where'] ) ? $this->parseWhere () : '';
		$sql .= isset ( $this->options ['order_by'] ) ? $this->parseOrderBy () : '';
		$sql .= isset ( $this->options ['limit'] ) ? $this->parseLimit () : '';
		try {
			$result = $this->db->executePrepareSQL ( $sql, $this->bind, true );
		}
		finally {
			$this->clear ();
		}
		return $result;
	}
	/**
	 * 删除记录
	 *
	 * @return string
	 */
	public function delete($options = array()) {
		if (! empty ( $options )) {
			$this->where ( $options );
		}
		$sql = 'DELETE FROM ' . $this->tableName . $this->parseWhere ();
		try {
			$result = $this->db->executePrepareSQL ( $sql, $this->bind );
		}
		finally {
			$this->clear ();
		}
		return $result;
	}
	
	/**
	 * 通过索引删除数据
	 *
	 * @param mixed $indexValue 索引值
	 * @param string $indexName 索引名
	 * @return Ambigous <>|NULL
	 */
	public function deleteByIndex($indexValue, $indexName = 'id') {
		return $this->where ( array (
				array (
						$indexValue,
						'=',
						$indexName 
				) 
		) )->delete ();
	}
	/**
	 * 更新记录
	 *
	 * @return string
	 */
	public function update($options = array()) {
		if (! empty ( $options )) {
			$this->where ( $options );
		}
		$sql = 'UPDATE ' . $this->tableName . $this->parseSet () . $this->parseWhere ();
		try {
			$result = $this->db->executePrepareSQL ( $sql, $this->bind );
		}
		finally {
			$this->clear ();
		}
		return $result;
	}
	/**
	 * 插入记录
	 *
	 * @return string
	 */
	public function insert($data) {
		$sql = 'INSERT INTO ' . $this->tableName;
		$sql .= ' (' . implode ( array_keys ( $data ), ',' ) . ') VALUES ';
		$sql .= $this->bindInsertData ( $data );
		try {
			$result = $this->db->executePrepareSQL ( $sql, $this->bind );
		}
		finally {
			$this->clear ();
		}
		return $result;
	}
	/**
	 * 统计数据量
	 *
	 * @param array $options
	 */
	public function count($options = array()) {
		if (! empty ( $options )) {
			$this->where ( $options );
		}
		$rs = $this->field ( 'count(*) AS count' )->select ();
		return $rs [0] ['count'];
	}
	/**
	 * 获取共多少页数据
	 *
	 * @param integer $pageSize
	 */
	public function getMaxPage($pageSize) {
		$count = $this->count ();
		$page = $count / $pageSize;
		if (is_double ( $page )) {
			$page = ( int ) $page;
			$page ++;
		}
		return ($page == 0) ? 1 : $page;
	}
	
	/**
	 * 批量插入记录
	 *
	 * @return string
	 */
	public function batchInsert($data) {
		$sql = 'INSERT INTO ' . $this->tableName;
		$sql .= ' (' . implode ( array_keys ( $data [0] ), ',' ) . ') VALUES ';
		$dataMaxIndex = count ( $data ) - 1;
		$index = 0;
		foreach ( $data as $item ) {
			$sql .= $this->bindInsertData ( $item );
			if ($index != $dataMaxIndex) {
				$sql .= ',';
			}
			$index ++;
		}
		try {
			$result = $this->db->executePrepareSQL ( $sql, $this->bind );
		}
		finally {
			$this->clear ();
		}
		return $result;
	}
	
	/**
	 * 持久化model对象
	 */
	public function save() {
		$rc = new ReflectionClass ( get_class ( $this ) );
		$properties = $rc->getProperties ( ReflectionProperty::IS_PUBLIC );
		$data = array ();
		foreach ( $properties as &$property ) {
			$key = $property->name;
			$value = $property->getValue ( $this );
			// 没有赋值的则忽略
			if ($value === NULL) {
				continue;
			}
			$data [$key] = $value;
		}
		$this->insert ( $data );
	}
	
	/**
	 * 绑定要插入的数据
	 *
	 * @param array $data
	 * @return string
	 */
	protected function bindInsertData($data) {
		$sql = '(';
		$maxIndex = count ( $data ) - 1;
		$index = 0;
		foreach ( $data as $value ) {
			$valueId = self::createValueId ();
			$sql .= $valueId;
			$this->bind [$valueId] = $value;
			if ($index != $maxIndex) {
				$sql .= ',';
			}
			$index ++;
		}
		$sql .= ')';
		return $sql;
	}
	/**
	 * 记录中的字段自增
	 *
	 * @param string $field
	 */
	public function inc($field) {
		$sql = 'UPDATE ' . $this->tableName . ' SET ' . $field . ' = ' . $field . '+1 ' . $this->parseWhere ();
		try {
			$result = $this->db->executePrepareSQL ( $sql, $this->bind );
		}
		finally {
			$this->clear ();
		}
		return $result;
	}
	
	/**
	 * 设置记录的某个字段值
	 *
	 * @param string $field 字段名
	 * @param string $value 字段值
	 * @return boolean
	 */
	public function setField($field, $value) {
		$data = array (
				$field => $value 
		);
		if (! isset ( $this->options ['set'] )) {
			$this->options ['set'] = $data;
		}
		else {
			$this->options ['set'] = array_merge ( $this->options ['set'], $data );
		}
		return $this;
	}
	/**
	 * 设置记录的字段值
	 *
	 * @param string $data 数据
	 * @return boolean
	 */
	public function setFields($data) {
		if (! isset ( $this->options ['set'] )) {
			$this->options ['set'] = $data;
		}
		else {
			$this->options ['set'] = array_merge ( $this->options ['set'], $data );
		}
		return $this;
	}
	/**
	 * 指定获取的数据
	 *
	 * @param integer $page 起始页号(下标从1开始)
	 * @param integer $pageSize 每页的数据量
	 */
	public function page($page = 1, $pageSize = 10) {
		$page = ($page > 0) ? $page : 1;
		$index = ($page - 1) * $pageSize;
		return $this->limit ( $index, $pageSize );
	}
	
	/**
	 * 指定查询数量
	 *
	 * @param mixed $offset 起始位置
	 * @param mixed $length 查询数量
	 * @return Model
	 */
	public function limit($offset, $length = null) {
		if (is_null ( $length ) && strpos ( $offset, ',' )) {
			list ( $offset, $length ) = explode ( ',', $offset );
		}
		$this->options ['limit'] = intval ( $offset ) . ($length ? ',' . intval ( $length ) : '');
		return $this;
	}
	
	/**
	 * 指定条件
	 *
	 * @param mixed $where 条件表达式
	 * @param string $logic 逻辑运算规则(AND OR NOR...)
	 * @return Model
	 */
	public function where($where, $logic = 'AND') {
		if (! empty ( $this->options ['where'] )) {
			$this->options ['where'] .= ' ' . $logic . ' ';
		}
		if (is_string ( $where )) {
			$this->options ['where'] .= ' (' . $where . ') ';
		}
		else if (is_array ( $where ) && ! empty ( $where )) {
			$sql = '(';
			$maxIndex = count ( $where ) - 1;
			for($index = 0; $index <= $maxIndex; $index ++) {
				list ( $name, $comparison, $value ) = $where [$index];
				$valueId = self::createValueId ();
				$sql .= "{$name} {$comparison} {$valueId}";
				$this->bind [$valueId] = $value;
				if ($index != $maxIndex) {
					$sql .= ' AND ';
				}
			}
			$sql .= ')';
			$this->options ['where'] .= $sql;
		}
		return $this;
	}
	/**
	 * 排序
	 *
	 * @param string|array $field 需要排序的字段
	 * @param bool $isDesc 是否降序
	 * @return Model
	 */
	public function orderBy($field, $isDesc = true) {
		if (is_string ( $field )) {
			$this->options ['order_by'] = $field;
		}
		else if (is_array ( $field )) {
			$this->options ['order_by'] = implode ( $field, ',' );
		}
		$this->options ['order_is_desc'] = $isDesc;
		return $this;
	}
	/**
	 * 字段匹配
	 *
	 * @param string $field 字段名
	 * @param string $value 与字段匹配的值
	 * @param string $logic 逻辑运算规则(AND OR NOR...)
	 * @return Model
	 */
	public function whereLike($field, $value, $logic = 'AND') {
		return $this->where ( array (
				array (
						$field,
						'like',
						$value 
				) 
		), $logic );
	}
	/**
	 * 指定字段的值
	 *
	 * @param string $field 字段名
	 * @param string $value 字段的值
	 * @param string $logic 逻辑运算规则(AND OR NOR...)
	 * @return Model
	 */
	public function whereEqual($field, $value, $logic = 'AND') {
		return $this->where ( array (
				array (
						$field,
						'=',
						$value 
				) 
		), $logic );
	}
	
	/**
	 * 解析order by子句
	 *
	 * @return string
	 */
	protected function parseOrderBy() {
		if (isset ( $this->options ['order_by'] )) {
			$desc = $this->options ['order_is_desc'] == true ? ' DESC ' : '';
			return ' ORDER BY ' . $this->options ['order_by'] . $desc;
		}
		return '';
	}
	
	/**
	 * 解析limit子句
	 *
	 * @return string
	 */
	protected function parseLimit() {
		if (isset ( $this->options ['limit'] )) {
			return ' LIMIT ' . $this->options ['limit'];
		}
		return '';
	}
	/**
	 * 解析set子句
	 *
	 * @return string
	 */
	protected function parseSet() {
		$sql = ' SET ';
		$maxIndex = count ( $this->options ['set'] ) - 1;
		$index = 0;
		foreach ( $this->options ['set'] as $name => $value ) {
			$valueId = self::createValueId ();
			$sql .= "{$name} = {$valueId}";
			$this->bind [$valueId] = $value;
			if ($index != $maxIndex) {
				$sql .= ' , ';
			}
			$index ++;
		}
		return $sql;
	}
	/**
	 * 解析where子句
	 *
	 * @return string
	 */
	protected function parseWhere() {
		if (isset ( $this->options ['where'] ))
			return ' WHERE ' . $this->options ['where'];
		return '';
	}
	/**
	 * 创建用于绑定变量的字符串
	 *
	 * @param number $count
	 * @return string multitype:string
	 */
	private static function createValueId($count = 1) {
		static $valueId = 0;
		if ($count == 1) {
			$valueId ++;
			return ':value' . $valueId;
		}
		$valueIds = array ();
		for($index = 1; $index <= $count; $index ++) {
			$valueIds [] = ':value' . $valueId;
			$valueId ++;
		}
		return $valueIds;
	}
	
	/**
	 * 获取模型对应的数据表名
	 * 如:UserInfoModel对应表名为user_info
	 *
	 * @param string $modelName
	 * @return string
	 */
	public function getTableName() {
		if (isset ( $this->tableName )) {
			return $this->tableName;
		}
		return get_table_name ( get_class ( $this ) );
	}
	
	/**
	 * 执行sql
	 *
	 * @param string $sql
	 * @param array $bind
	 */
	public function query($sql, $bind = array()) {
		return $this->db->query ( $sql, $bind );
	}
	/**
	 * 执行sql
	 *
	 * @param string $sql
	 * @param array $bind
	 */
	public function exec($sql, $bind = array()) {
		return $this->db->exec ( $sql, $bind );
	}
	/**
	 * 返回由上次的INSERT或UPDATE语句为AUTO_INCREMENT列生成的值
	 *
	 * @param string $name
	 * @return string
	 */
	public function getLastInsertId($name) {
		return $this->db->getPDO ()->lastInsertId ( $name );
	}
	/**
	 * 清空model数据
	 */
	protected function clear() {
		$this->options = array ();
		$this->bind = array ();
	}
	/**
	 * 创建model对应的数据表
	 */
	public function createTable() {
		// table不存在时才创建
		$sql = 'CREATE TABLE IF NOT EXISTS ' . $this->tableName . ' (';
		$rc = new ReflectionClass ( get_class ( $this ) );
		$properties = $rc->getProperties ( ReflectionProperty::IS_PUBLIC );
		$maxIndex = count ( $properties ) - 1;
		$index = 0;
		foreach ( $properties as $property ) {
			$doc = $property->getDocComment ();
			preg_match ( '/#.*#/', $doc, $matches );
			$states = implode ( array_filter ( split ( '#', $matches [0] ) ), ' ' );
			$sql .= $property->name . ' ' . $states;
			if ($index != $maxIndex) {
				$sql .= ',';
				$index ++;
			}
		}
		$sql .= ')';
		return $this->db->exec ( $sql );
	}
	/**
	 * 删除model对应的数据表
	 *
	 * @return number
	 */
	public function dropTable() {
		$sql = 'DROP TABLE ' . $this->tableName;
		return $this->db->exec ( $sql );
	}
	/**
	 * 提交事务
	 */
	public static function commit() {
		Database::commit ();
	}
	/**
	 * 开始事务
	 */
	public static function beginTransaction() {
		Database::beginTransaction ();
	}
	/**
	 * 回滚事务
	 */
	public static function rollBack() {
		Database::rollBack ();
	}
}
